﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Diagnostics;

namespace TestDoc
{
    /// <summary>
    /// Returns list of tests in a dll
    /// </summary>
    public sealed class TestParser
    {
        #region Constants
        #endregion

        #region Private members

        private readonly Assembly _assembly;
        #endregion

        #region Constructors
        /// <summary>
        /// Creats an instance of parser
        /// </summary>
        /// <param name="target">name of dll to parse for tests</param>
        public TestParser(Assembly target)
        {
            _assembly = target;
        }
        #endregion

        /// <summary>
        /// Gets tests
        /// </summary>
        /// <returns>returns list of methods</returns>
        public IList<MethodInfo[]> GetTests()
        {
            IList<MethodInfo[]> methods = GetMethodsInAssembly(_assembly.GetTypes());
            IList<MethodInfo[]> assemblyTestMethods = GetAssemblyTestMethods(methods);

            //return methods;
            return assemblyTestMethods;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="assemblyMethods"></param>
        /// <returns></returns>
        public IList<MethodInfoSmall> FormatTestReport(IList<MethodInfo[]> assemblyMethods)
        {
            IList<MethodInfoSmall> output = new List<MethodInfoSmall>();

            foreach (var type in assemblyMethods)
            {
                var m = new MethodInfoSmall(MethodType.Namespace, FormatMethodName(type[0].DeclaringType.Namespace));

                output.Add(m);
                m = new MethodInfoSmall(MethodType.ClassName,
                                        FormatMethodName(FormatMethodName(type[0].DeclaringType.Name)));
                output.Add(m);

                foreach (var method in type)
                {
                    m = new MethodInfoSmall(MethodType.MethodName, FormatMethodName(method));
                    output.Add(m);
                }
            }

            return output;
        }

        private static string FormatMethodName(string formattedMethodName)
        {
            formattedMethodName = Regex.Replace(formattedMethodName, "_", " ");
            formattedMethodName = FormatSpacePrecedesCapitalLetters(formattedMethodName);

            return formattedMethodName;
        }

        private static string FormatMethodName(MethodInfo method)
        {
            string formattedMethodName = method.Name;


            // find capital letters and ensure a space precedes.
            formattedMethodName = FormatMethodName(formattedMethodName);

            return formattedMethodName;
        }

        private static string FormatSpacePrecedesCapitalLetters(string methodName)
        {
            string formattedText = string.Empty;

            string[] words = Regex.Split(methodName, "(?<!^)(?=[A-Z])");

            for (int i = 0; i < words.Length; i++)
            {
                string word1 = words[i].Trim();
                formattedText += word1 + " ";
            }

            return formattedText.Trim();
        }

        private static IList<MethodInfo[]> GetAssemblyTestMethods(IEnumerable<MethodInfo[]> methods)
        {
            IList<MethodInfo[]> assemblyTestMethods = new List<MethodInfo[]>();

            foreach (var type in methods)
            {

                MethodInfo[] typeTestMethods = GetTypeTestMethods(type);
                if (typeTestMethods.Length > 0)
                    assemblyTestMethods.Add(typeTestMethods);
            }

            return assemblyTestMethods;
        }

        private static MethodInfo[] GetTypeTestMethods(ICollection<MethodInfo> type)
        {
            int i = 0;
            var typeTestMethods = new MethodInfo[type.Count];
            foreach (MethodInfo method in type)
            {
                if (IsTestMethod(method))
                {
                    typeTestMethods[i] = method;
                    i++;
                }
            }

            var result = new MethodInfo[i];
            for (int y = 0; y < i; y++)
            {
                if (typeTestMethods[y] != null)
                    result[y] = typeTestMethods[y];
            }

            return result;
        }

        private static bool IsTestMethod(MemberInfo method)
        {
            bool isTestMethod = false;
            var attributes = CustomAttributeData.GetCustomAttributes(method);
            foreach (CustomAttributeData attribute in attributes)
            {
                if (IsTestAttribute(attribute))
                {
                    isTestMethod = true;
                    break;
                }
            }

            return isTestMethod;
        }

        private static bool IsTestAttribute(CustomAttributeData att)
        {
            return IsNunitAttribute(att) || IsMsTestAttribute(att);
        }

        private static bool IsMsTestAttribute(CustomAttributeData att)
        {

            return att.ToString().ToUpper().Contains("Microsoft.VisualStudio.TestTools.UnitTesting.".ToUpper());
        }

        private static bool IsNunitAttribute(CustomAttributeData att)
        {
            return att.ToString().ToUpper().Contains("NUnit.Framework".ToUpper());
        }

        private static IList<MethodInfo[]> GetMethodsInAssembly(IEnumerable<Type> types)
        {
            IList<MethodInfo[]> methods = new List<MethodInfo[]>();

            foreach (var type in types)
            {
                MethodInfo[] info = type.GetMethods();
                methods.Add(info);
            }

            return methods;
        }


    }
}
